package com.hs.netty.server.handler;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.hs.netty.common.entity.MonitorVoDO;
import com.hs.netty.server.components.ChannelRepository;
import com.hs.netty.common.protobuf.Command;
import com.hs.netty.common.protobuf.Message;
import io.netty.channel.*;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;
import io.netty.util.Attribute;
import io.netty.util.AttributeKey;
import io.netty.util.ReferenceCountUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.*;


/**
 * 实现心跳的hander  支持超时断开客户端避免浪费资源
 *
 * @param
 * @Author: myzf
 * @Date: 2019/2/23 13:25
 */
@Component(value = "serverHeartHandler")
@ChannelHandler.Sharable
public class ServerHeartHandler extends ChannelInboundHandlerAdapter {
    public Logger log = LoggerFactory.getLogger(this.getClass());
    private final DateFormat offLine = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    private final DateFormat onLine = new SimpleDateFormat("HH:mm:ss");

    private final AttributeKey<String> clientInfo = AttributeKey.valueOf("clientInfo");
    // 设置6秒检测chanel是否接受过心跳数据
    private static final int READ_WAIT_SECONDS = 3;

    // 定义客户端没有收到服务端的pong消息的最大次数
    private static final int MAX_UN_REC_PING_TIMES = 3;

    // 失败计数器：未收到client端发送的ping请求
    private int unRecPingTimes = 0;

    @Autowired
    @Qualifier("channelRepository")
    private ChannelRepository channelRepository;


    /*空闲触发器 心跳基于空闲实现*/
    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        if (evt instanceof IdleStateEvent) {
            IdleStateEvent event = (IdleStateEvent) evt;
            String type = "";
            if (event.state() == IdleState.READER_IDLE) {
                type = "read idle";
            } else if (event.state() == IdleState.WRITER_IDLE) {
                type = "write idle";
            } else if (event.state() == IdleState.ALL_IDLE) {
                type = "all idle";
            }
            if (unRecPingTimes >= MAX_UN_REC_PING_TIMES) {
                // 连续超过N次未收到client的ping消息，那么关闭该通道，等待client重连
                ctx.channel().close();
            } else {
                // 失败计数器加1

                unRecPingTimes++;
            }
            log.debug(ctx.channel().remoteAddress() + "超时类型：" + type);
        } else {
            super.userEventTriggered(ctx, evt);
        }
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        Message.MessageBase msgBase = (Message.MessageBase) msg;
        String clientId = msgBase.getClientId();
        Channel ch = channelRepository.get(clientId);
        if (null == ch) {
            ch = ctx.channel();
            channelRepository.put(clientId, ch);
        }
        /*认证处理*/
        if (msgBase.getCmd().equals(Command.CommandType.PUSH_DATA)) {
            //收到 客户端 PUSH_DATA 推送消息
            Map map = JSONObject.parseObject(msgBase.getData(), Map.class);
//            OnlineOffLine(map.get("clientName").toString(), true);
            log.info("开始监控  客户端 : [" + map.get("clientName") + "] || 监控设备 :  [" + map.get("deviceName") + "]");
        } else if (msgBase.getCmd().equals(Command.CommandType.AUTH)) {
            // 收到 客户端 AUTH 认证消息
            log.info("收到客户端 " + clientId + " 的建立连接请求...");
            Attribute<String> attr = ctx.channel().attr(clientInfo);
            attr.set(msgBase.getData());
            channelRepository.put(clientId, ctx.channel());
            //回写数据
            ctx.writeAndFlush(createData(clientId, Command.CommandType.AUTH_BACK, "客户端 [" + clientId + "] 与服务器建立连接成功！").build());
        } else if (msgBase.getCmd().equals(Command.CommandType.PING)) {
            //收到 客户端 Ping消息
            ctx.writeAndFlush(createData(clientId, Command.CommandType.PONG, "服务器响应客户端Id是 [" + clientId + "] 的心跳包").build());
            unRecPingTimes = 0;
        } else {
            if (ch.isOpen()) {
                //触发下一个handler
                ctx.fireChannelRead(msg);

            }
        }
        ReferenceCountUtil.release(msg);
    }


    /**
     * 构建不同类型的数据 基于protobuff
     *
     * @param
     * @Author: myzf
     * @Date: 2019/2/23 13:25
     */
    private Message.MessageBase.Builder createData(String clientId, Command.CommandType cmd, String data) {
        Message.MessageBase.Builder msg = Message.MessageBase.newBuilder();
        msg.setClientId(clientId);
        msg.setCmd(cmd);
        msg.setData(data);
        return msg;
    }

    @Autowired
    RedisTemplate redisTemplate;

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        log.error("发生了异常-------->" + cause.getMessage());
    }


    /**
     * 处理断开
     */
    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        if (!ctx.channel().isActive()) {
            Attribute<String> attr = ctx.channel().attr(clientInfo);
            String clientId = attr.get();
            Map map = JSONObject.parseObject(clientId, Map.class);
            String[] deviceName = map.get("deviceName").toString().split("\\s+");
            for (String s : deviceName) {
//                log.warn("删除" + map.get("clientName") + "客户端的" + s + "设备");
                redisTemplate.opsForHash().delete("devOnline", s);
//                OnlineOffLine(map.get("clientName").toString(), false);
            }
            log.warn("客户端 [" + map.get("clientName") + "] 已断开连接...删除devOnline设备------> [" + map.get("deviceName") + "]");
            super.channelInactive(ctx);
        }
    }

    /**
     * 网络问题触发
     *
     * @param ctx
     * @throws Exception
     */
    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
        //log.warn("网络异常.......");
        super.handlerRemoved(ctx);
    }

    @Autowired
    StringRedisTemplate stringRedisTemplate;

    public void OnlineOffLine(String ClientName, boolean flag) {
        MonitorVoDO monitorVoDO = new MonitorVoDO();
        if (flag) {
            monitorVoDO.setValueList(null);
            monitorVoDO.setClientId(ClientName);
            monitorVoDO.setDateList(onLine.format(new Date()));
        } else {
            monitorVoDO.setValueList(null);
            monitorVoDO.setClientId("已离线 # " + ClientName + " #");
            monitorVoDO.setDateList(offLine.format(new Date()));
        }

        stringRedisTemplate.opsForList().leftPush("heart:monitor:" + ClientName, JSON.toJSONString(monitorVoDO));
    }
}
